﻿using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Linq;

namespace Silverstone
{
    public static class CommandManager
    {
        /// <summary>
        ///     A dictionary of commands against a list of the buttons which are raising those commands
        /// </summary>
        private static readonly Dictionary<ICommand, List<WeakReference>> registeredCommands = 
            new Dictionary<ICommand, List<WeakReference>>();

        /// <summary>
        ///     Initialises the command manager - call on application startup
        /// </summary>
        public static void Initialise()
        {
            // Subscribe to input events on the root visual and evaluate all registered CommandBase instances
            UIElement uiElement = Application.Current.RootVisual;
            if (uiElement != null)
            {
                uiElement.KeyDown += delegate { EvaluateAllRegisteredCommands(); };
                uiElement.MouseLeftButtonDown += delegate { EvaluateAllRegisteredCommands(); };
            }
        }

        public static void InvalidateRequerySuggested()
        {
            foreach (ICommand command in registeredCommands.Keys)
            {
                EnableOrDisableCommandSources(command);
            }
        }

        internal static void HandleCommandPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ButtonBase button = d as ButtonBase;
            if (button != null)
            {
                button.Click += HandleExecuteCommand;

                CommandBase oldCommand = e.OldValue as CommandBase;
                if (oldCommand != null)
                {
                    UnregisterCommandForAutoInvalidate(button, oldCommand);
                }

                CommandBase newCommand = e.NewValue as CommandBase;
                if (newCommand != null)
                {
                    RegisterCommandForAutoInvalidate(button, newCommand);
                }
            }
        }

        private static void RegisterCommandForAutoInvalidate(ButtonBase button, CommandBase command)
        {
            command.CanExecuteChanged += HandleCommandCanExecuteChanged;
            if (registeredCommands.ContainsKey(command))
            {
                registeredCommands[command].Add(new WeakReference(button));
            }
            else
            {
                registeredCommands[command] = new List<WeakReference> { new WeakReference(button) };
            }
            EnableOrDisableCommandSources(command);
        }

        private static void EvaluateAllRegisteredCommands()
        {
            foreach (ICommand command in registeredCommands.Keys)
            {
                CommandBase commandBase = command as CommandBase;
                if (commandBase != null)
                {
                    commandBase.InvalidateRequerySuggested();
                }
            }
        }

        private static void UnregisterCommandForAutoInvalidate(ButtonBase button, CommandBase command)
        {
            command.CanExecuteChanged -= HandleCommandCanExecuteChanged;
            if (registeredCommands.ContainsKey(command))
            {
                WeakReference reference = registeredCommands[command].First(w => w.Target == button);
                registeredCommands[command].Remove(reference);
                if (registeredCommands[command].Count == 0)
                {
                    registeredCommands.Remove(command);
                }
            }
        }

        public static void HandleCommandParameterPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ICommand command = CommandBinder.GetCommand(d);
            if (command != null)
            {
                EnableOrDisableCommandSources(command);
            }
        }

        private static void HandleExecuteCommand(object sender, RoutedEventArgs e)
        {
            DependencyObject d = (DependencyObject)sender;
            ICommand command = CommandBinder.GetCommand(d);
            object parameter = CommandBinder.GetCommandParameter(d);

            if (command != null && command.CanExecute(parameter))
            {
                command.Execute(parameter);
            }
        }

        private static void HandleCommandCanExecuteChanged(object sender, EventArgs e)
        {
            ICommand command = sender as ICommand;
            if (command != null)
            {
                EnableOrDisableCommandSources(command);
            }
        }

        private static void EnableOrDisableCommandSources(ICommand command)
        {
            if (registeredCommands.ContainsKey(command))
            {
                foreach (WeakReference weakReference in registeredCommands[command])
                {
                    ButtonBase buttonBase = (ButtonBase) weakReference.Target;
                    buttonBase.IsEnabled = command.CanExecute(CommandBinder.GetCommandParameter(buttonBase));
                }
            }
        }
    }
}
